.. _глава assert и enforce:

assert и enforce
================

.. index::
    assert
    enforce

В предыдущих двух главах мы видели, как исключения и утверждения области видимости
используются для обеспечения корректности программы. assert - это еще один мощный инструмент для достижения
той же цели, гарантируя, что определенные предположения, на которых основана программа
действительны.
Иногда бывает трудно решить, следует ли выбросить исключение или вызвать
assert. Я буду использовать assert во всех приведенных ниже примерах без особого обоснования.
Разницу я объясню позже в этой главе.
Хотя это не всегда очевидно, программы полны предположений. Например,
следующая функция написана в предположении, что оба параметра возраста
больше или равны нулю:


.. code-block:: d

    double averageAge(double first, double second)
    {
        return (first + second) / 2;
    }


TODO Check translation
Несмотря на то, что в программе может не оказаться отрицательного значения возраста
функция все равно выведет среднее значение, которое может быть использовано в программе
В результате программа может продолжать работать с неверными данными.
В качестве другого примера можно привести следующую функцию, предполагающую, что она всегда будет
вызываться двумя командами: "петь" или "танцевать":

.. code-block:: d

    void applyCommand(string command)
    {
        if (command == "sing")
        {
            robotSing();
        }
        else
        {
            robotDance();
        }
    }


Из-за этого предположения функция robotDance() будет вызываться для каждой
команды, отличной от "петь", действительной или недействительной.
Когда такие предположения хранятся только в голове программиста, программа
может работать некорректно. Операторы assert проверяют предположения и
немедленно завершают программу, если они не соответствуют действительности.

Синтаксис
---------

assert можно использовать двумя способами:

.. code-block:: d

    assert(logical_expression);
    assert(logical_expression, message);

Логическое выражение представляет собой предположение о программе. assert
производит вычисление этого выражения, чтобы подтвердить предположение. Если значение логического
равно true, то предположение считается верным. В противном случае
предположение недействительно, и будет выброшена ошибка AssertError.
Как следует из названия, это исключение наследуется от Error, и, как вы, возможно.
помните из главы :ref:`Исключения<глава_39_start>`, исключения, наследуемые
от Error, никогда не должны быть пойманы. Важно, чтобы программа была
чтобы программа сразу же завершилась, а не продолжала работать при неверных предположениях.
Два неявных предположения функции averageAge(), приведенные выше, могут быть сформулированы с помощью
двумя вызовами assert, как в следующей функции:

.. code-block:: d

    double averageAge(double first, double second)
    {
        assert(first >= 0);
        assert(second >= 0);
        return (first + second) / 2;
    }

    void main()
    {
        auto result = averageAge(-1, 10);
    }


Эти утверждающие проверки имеют значение "при условии, что оба возраста
больше или равны нулю". Можно также считать, что это означает "эта функция
может работать корректно, только если оба возраста больше или равны нулю".
Каждое утверждение проверяет свое предположение и завершает программу с ошибкой
AssertError, если оно не соответствует действительности:

.. code-block:: console

    core.exception.AssertError@deneme(2): Assertion failure

Часть после символа @ в сообщении указывает исходный файл и номер строки
номер проверки assert, которая завершилась неудачей. Согласно приведенному выше выводу, утверждение
assert, который не сработал, находится в строке 2 файла deneme.d.
Другой синтаксис assert позволяет выводить пользовательское сообщение, когда проверка assert
при неудачной проверке:

.. code-block:: d

    assert(first >= 0, "Age cannot be negative.");

Вывод

.. code-block:: console

    core.exception.AssertError@deneme.d(2): Age cannot be negative.

Иногда считается, что программа никогда не должна попасть в некоторую ветку.
В таких случаях принято использовать литерал false в качестве логического выражения
для отказа в проверке утверждения. Например, чтобы указать, что функция applyCommand()
никогда не будет вызвана с командой, отличной от "петь" и "танцевать", и
для защиты от такой возможности можно вставить assert(false) в ветку, которая никогда не должна исполняться

.. code-block:: d

    void applyCommand(string command)
    {
        if (command == "sing")
        {
            robotSing();
        }
        else if (command == "dance")
        {
            robotDance();
        }
        else
        {
            assert(false);
        }
    }

Функция гарантированно работает только с двумя командами, о которых она знает о которых она знает.

*(Примечание: Альтернативным вариантом здесь может быть использование оператора final switch* (:ref:`Оператор switch и case<глава_30_start>`))

static assert
------------------

Поскольку проверки assert предназначены для корректного выполнения программ, они применяются, когда
когда программа действительно выполняется. Могут быть и другие проверки, касающиеся
структуры программы, которые могут применяться даже во время компиляции.
static assert - это аналог assert, который применяется во время компиляции.
Его преимущество в том, что он не позволяет даже скомпилировать программу, которая в противном случае
в противном случае выполнялась бы некорректно. Естественным требованием является вычислимость логического выражения во время компиляции.

Например, если предположить, что заголовок меню будет выводиться на устройство вывода устройстве вывода, имеющем ограниченную ширину, следующее статическое утверждение гарантирует, что он никогда не будет
никогда не будет шире этого ограничения:

.. code-block:: d

    enum dstring menuTitle = "Command Menu";
    static assert(menuTitle.length <= 16);

Обратите внимание, что строка определена как перечисление, поэтому ее длина может быть вычислена во время время компиляции.
Предположим, что программист изменит это название, сделав его более описательным:

.. code-block:: d

    enum dstring menuTitle = "Directional Commands Menu";
    static assert(menuTitle.length <= 16);

Статическая проверка assert предотвращает компиляцию программы:

.. code-block:: console

    Error: static assert (25u <= 16u) is false

Это напомнит программисту об ограничении устройства вывода.
static assert еще более полезен при использовании в шаблонах. Мы рассмотрим
шаблоны в последующих главах.

assert even if absolutely true
------------------------------

Я подчеркиваю "абсолютно истинно", потому что предположения о программе никогда не
могут быть ложными. Большое количество программных ошибок вызвано
предположениями, которые считаются абсолютно истинными.
По этой причине пользуйтесь проверками assert, даже если они кажутся излишними.
Давайте рассмотрим следующую функцию, которая возвращает дни месяцев в заданном
году:

.. code-block:: d

    int[] monthDays(int year)
    {
        int[] days = [31, februaryDays(year),31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        assert((sum(days) == 365) || (sum(days) == 366));
        return days;
    }

Эта проверка утверждения может показаться излишней, поскольку функция
естественно вернет либо 365, либо 366. Однако эти проверки защищают от
потенциальных ошибок даже в функции februaryDays(). Например.
программа будет завершена, если februaryDays() вернет 30.
Еще одна, казалось бы, ненужная проверка может гарантировать, что длина среза
всегда будет равна 12:

Эта проверка утверждения может показаться излишней, поскольку функция
естественно вернет либо 365, либо 366. Однако эти проверки защищают от
потенциальных ошибок даже в функции februaryDays(). Например.
программа будет завершена, если februaryDays() вернет 30.
Еще одна, казалось бы, ненужная проверка может гарантировать, что длина среза
всегда будет равна 12:

.. code-block:: d

    assert(days.length == 12);

Таким образом, непреднамеренное удаление или добавление элементов в срез также будет
пойманы. Такие проверки являются важными инструментами для обеспечения корректности программы.
Утверждение также является фундаментальным инструментом, который используется в модульном тестировании и контрактном
программирования, о которых мы поговорим в следующих главах.

Нет значений и побочныхэфектов
------------------------------

Мы видели, что выражения могут создавать значения и иметь побочные эффекты.
assert - это проверка, не производящая значений.
Кроме того, сама проверка assert не имеет побочных эффектов. Она дается
Стандарт D также требует, чтобы логическое выражение не имело побочных эффектов
assert - это условие, которое не изменяет состояние программы и только
должно оставаться структурой, проверяющей предположения.

Отключение утверждений assert
-----------------------------

Поскольку assert проверяет корректность программы, их можно считать ненужными после того, как
программа была достаточно протестирована. Кроме того, поскольку проверки assert не производят
никаких значений и не имеют побочных эффектов, их удаление из программы не должно
не должно иметь никакого значения.
Ключ компилятора -release заставляет игнорировать проверки assert, как будто
как если бы они никогда не были включены в программу:

.. code-block:: console

    $ dmd deneme.d -release

Это позволит программам работать быстрее за счет отсутствия вычислений потенциально медленных
логических выражений проверок assert.
В качестве исключения, проверки assert, имеющие в качестве логического выражения литерал false (или 0)
не отключаются даже при компиляции программы с параметром
-release. В качестве исключения, при значении false или значении по умолчанию, которое автоматически преобразуется в
вызываемые утверждения могут быть удалены из программы даже при компиляции с -release.
не удаляются. Это происходит потому, что проверки assert(false) никогда не
Они определяют строки, которые никогда не должны быть достигнуты, и каждый раз, попадание в этот блок кода
всегда будет ошибочным.


enforce
--------

Не каждая неожиданная ситуация является признаком ошибки в программе. Программы
также могут столкнуться с неожиданными входными данными и неожиданным состоянием окружающей среды. Например,
например, данные, которые вводит пользователь, не должны проверяться с помощью  утверждения, потому что недействительные данные не имеют никакого отношения к корректности
потому что недействительные данные не имеют никакого отношения к корректности самой программы. В таких случаях уместно бросать исключения, как мы это делали в предыдущих программах.
std.exception.enforce - это удобный способ выброса исключений. Для
Например, предположим, что исключение должно быть выброшено, когда определенное
условие не выполняется:

.. code-block:: d

    if (count < 3)
    {
        throw new Exception("Must be at least 3.");
    }

enforce() - это обертка вокруг проверки условия и оператора throw.
Ниже приведен эквивалент предыдущего кода:

.. code-block:: d

    import std.exception;
    // ...
    enforce(count >= 3, "Must be at least 3.");

Обратите внимание, что логическое выражение отрицается по сравнению с оператором if. Теперь в нем
указывает, что именно выполняется.

Как использовать
-----------------

assert предназначен для выявления ошибок программистов. Условия, от которых защищает assert
от которых защищает assert в функции monthDays() и переменной menuTitle, приведены выше.
ошибки программистов.
Иногда трудно решить, стоит ли полагаться на проверку assert или
выбросить исключение. Решение должно основываться на том, связана ли ситуация с тем, как была написана программа.
В противном случае программа должна выбрасывать исключение, когда невозможно
выполнить задачу. enforce() выразительна и удобна при выбрасывании
исключения. Еще один момент, который следует учитывать, - можно ли как-то исправить неожиданную ситуацию
каким-либо образом. Если программа не может сделать ничего особенного, даже просто вывести
сообщение об ошибке, связанной с проблемой с некоторыми входными данными, то уместно
выбросить исключение. Таким образом, пользователи кода, который выбросил исключение, смогут
поймать его, чтобы сделать что-то особенное для выхода из состояния ошибки.


Упражнения
-----------

#. Следующая программа содержит ряд проверок утверждений. Скомпилируйте и запустите
чтобы обнаружить ошибки, выявленные этими проверками.
Программа принимает от пользователя время начала и продолжительность и вычисляет
время окончания, добавляя продолжительность к времени начала:

.. code-block:: d

    10 hours and 8 minutes after 06:09 is 16:17.

Обратите внимание, что эту задачу можно написать гораздо чище, определив
типы struct. Мы будем обращаться к этой программе в последующих главах.

.. code-block:: d

    import std.stdio;
    import std.string;
    import std.exception;

    /* Reads the time as hour and minute after printing a * message. */
    void readTime(string message,
        out int hour,
        out int minute)
    {
        write(message, "? (HH:MM) ");
        readf(" %s:%s", &hour, &minute);
        enforce((hour >= 0) && (hour <= 23) && (minute >= 0) && (minute <= 59), "Invalid time!");
    }

    /* Returns the time in string format. */
    string timeToString(int hour, int minute)
    {
        assert((hour >= 0) && (hour <= 23));
        assert((minute >= 0) && (minute <= 59));
        return format("%02s:%02s", hour, minute);
    }

    /* Adds duration to start time and returns the result as the * third pair of parameters. */
    void addDuration(int startHour, int startMinute,
        int durationHour, int durationMinute,
        out int resultHour, out int resultMinute)
    {
        resultHour = startHour + durationHour;
        resultMinute = startMinute + durationMinute;
        if (resultMinute > 59)
        {
            ++resultHour;
        }
    }

    void main()
    {
        int startHour;
        int startMinute;
        readTime("Start time", startMinute, startHour);
        int durationHour;
        int durationMinute;
        readTime("Duration", durationHour, durationMinute);
        int endHour;
        int endMinute;
        addDuration(startHour, startMinute, durationHour, durationMinute, endHour, endMinute);
        writefln("%s hours and %s minutes after %s is %s.", durationHour, durationMinute, timeToString(startHour, startMinute),
            timeToString(endHour, endMinute));
    }


Запустите программу и введите 06:09 в качестве времени начала и 1:2 в качестве продолжительности.
Обратите внимание, что программа завершается нормально.
Примечание: Вы можете заметить проблему с выводом данных. Пока не обращайте внимания на эту проблему, поскольку
вы скоро обнаружите ее с помощью проверок assert.

#. На этот раз введите 06:09 и 15:2. Обратите внимание, что программа завершается с ошибкой
   AssertError. Перейдите к той строке программы, которая указана в сообщении assert
   и посмотрите, какая из проверок assert завершилась неудачно. Возможно, потребуется
   время, чтобы выяснить причину этого конкретного сбоя.
#. Введите 06:09 и 20:0 и посмотрите, что та же самая проверка assert по-прежнему не работает, и
   исправьте и эту ошибку.
#. Измените программу так, чтобы она выводила время в 12-часовом формате с индикатором "am" или "pm"
   индикатором.

Решения :ref:`assert и enforce<assert и enforce>`
